﻿Imports System.Threading
Imports SilverMemory.Engine.GameState

''' <summary>
''' the manager of the game
''' </summary>
''' <remarks></remarks>
Public Class Game

  Private currentState As AbstractGameState
  Private cardsFactory As ICardsFactory
  Public Event GameStateChanged(ByVal sender As Object, ByVal e As EventArgs)

  ''' <summary>
  ''' change the gamestate
  ''' </summary>
  ''' <typeparam name="T">the type of gamestate to change to</typeparam>
  Private Sub toGameState(Of T As {AbstractGameState, New})()
    'create new gamestate of given type
    Dim newState As New T()
    newState.CopyDataFrom(Me.currentState)

    'replace current gamestate
    GameState = newState
  End Sub

  ''' <summary>
  ''' move to next gamestate (adviced by gamestate after doturn or otherwise)
  ''' </summary>
  Private Sub GameState_NewAdviceAvailable(ByVal sender As Object, ByVal e As EventArgs)
    'replace current gamestate
    Dim advicingState As AbstractGameState = CType(sender, AbstractGameState)
    GameState = advicingState.NextGameState
  End Sub

  ''' <summary>
  ''' state of the game. The type is the phase, the contents make up the state itself
  ''' </summary>
  Public Property GameState() As AbstractGameState
    Get
      Return currentState
    End Get
    Private Set(ByVal value As AbstractGameState)
      SyncLock currentState
        'overwrite state
        currentState = value
        'add handler for new advices
        AddHandler currentState.NewAdviceAvailable, AddressOf GameState_NewAdviceAvailable
        'notice the gamestate itself that is has been activated
        currentState.OnActivate()
        'publish the new game state
        RaiseEvent GameStateChanged(Me, New EventArgs)
      End SyncLock
    End Set
  End Property

  ''' <summary>
  ''' caller gets the gamestate, the gamestate is removed from the game
  ''' and replaced by the lockedgamestate. Lock should be removed and gamestate
  ''' restored by calling overloaded DoTurn with the gamestate as parameter.
  '''  This method should be used by an artificial player
  ''' to make sure no input from the UI reaches the gamestate.
  ''' </summary>
  Public Function LockGamestateExclusively() As AbstractGameState
    SyncLock Me.currentState
      Dim stateToLock = Me.currentState
      'bypass publishing events
      Me.currentState = New LockedGameState()
      Me.currentState.CopyDataFrom(stateToLock)
      'return the original gamestate
      Return stateToLock
    End SyncLock
  End Function

  ''' <summary>
  ''' constructor
  ''' </summary>
  ''' <param name="cardsFactory">factory that produces the cards</param>
  Public Sub New(ByVal cardsFactory As ICardsFactory)
    'initial game state, circumvent publishing event
    Me.currentState = New LockedGameState()

    Me.cardsFactory = cardsFactory
    AddHandler Me.cardsFactory.CardsReady, AddressOf cardsFactory_CardsReady
  End Sub

  ''' <summary>
  ''' cardsfactory is ready
  ''' </summary>
  Private Sub cardsFactory_CardsReady(ByVal sender As Object, ByVal e As EventArgs)
    'get cards and put in state
    Me.currentState.InitializeCards(Me.cardsFactory.GetUniqueCards())
    'unlock state 
    toGameState(Of ReadyToStartGameState)()
  End Sub

  ''' <summary>
  ''' start the game
  ''' </summary>
  ''' <param name="playerFactory">Factory with which players can be made.</param>
  Public Sub Start(ByVal playerFactory As IPlayerFactory)
    Me.currentState.CopyPlayersListFrom(playerFactory.GetPlayers())

    'set player turn
    Me.currentState.FirstPlayer().SetTurnState(Player.TurnStateE.MayMakeFirstMove)

    'start da game
    Me.toGameState(Of FirstMoveGameState)()
  End Sub


  ''' <summary>
  ''' a card is selected. Act accordingly.
  ''' </summary>
  ''' <param name="card">The selected card</param>
  Public Sub CardSelected(ByVal card As Card)
    If card.Facing = Engine.Card.Face.Down And Not card.Taken Then
      SyncLock currentState

        'let gamestate react to do the turn
        GameState.DoTurn(card)

      End SyncLock
    End If
  End Sub

  ''' <summary>
  ''' a card is selected. Act accordingly. This method should be preceded by a call to
  ''' LockGamestateExclusively. The call to this method returns the gamestate into the game.
  ''' </summary>
  ''' <param name="card">the selected card</param>
  ''' <param name="lockedGamestate">The gamestate to unlock</param>
  Public Sub CardSelected(ByVal card As Card, ByVal lockedGamestate As AbstractGameState)
    'check pre
    If Not TypeOf Me.currentState Is LockedGameState Then
      Throw New InvalidOperationException("Method can only be called in case of a locked gamestate. Gamestate is now: " & Me.currentState.GetType().Name)
    End If

    SyncLock Me.currentState
      'restore gamestate
      Me.currentState = lockedGamestate

      'do turn
      CardSelected(card)
    End SyncLock
  End Sub

End Class
